package com.sylli.oeuf.server.game.logic;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import com.sylli.oeuf.server.game.logic.invocation.DispatcherEntry;
import com.sylli.oeuf.server.game.logic.invocation.InvocationEntry;
import com.sylli.oeuf.server.game.logic.invocation.InvocationEvent;
import com.sylli.oeuf.server.game.logic.invocation.InvocationParameter;
import com.sylli.oeuf.server.game.logic.invocation.LogicObjectFactory;
import com.sylli.oeuf.server.object.transferable.PostBattleResponseTO;

public class BattleEventDispatcher {
	
	private Map<InvocationEvent, SortedSet<DispatcherEntry>> eventTable;
	
	private PostBattleResponseTO response;
	
	public void addDispatcherEntry(DispatcherEntrySource source) {
		for (InvocationEvent event : source.getAvailableHandlers()) {
			registerEventListener(event, source.getDispatcherEntries(event));
		}
	}
	
	public void addDispatcherPassiveSkillEntry(DispatcherEntrySource source, int rank) {
		// just to make sure it wont read the ACTIVE event.s
		for (InvocationEvent event : source.getAvailableHandlers()) {
			if (!event.equals(InvocationEvent.STD_IE_ON_ACTIVE)) {
				for (DispatcherEntry en : source.getDispatcherEntries(event)) {
					en.getParams().setRank(rank);
					registerEventListener(event, en);
				}
			}
		}
	}
	
	public BattleEventDispatcher(PostBattleResponseTO response) {
		this.response = response;
		eventTable = new HashMap<InvocationEvent, SortedSet<DispatcherEntry>>();
	}
	
	public void dispatchEvent(InvocationEvent event, BattleActor owner) {
		dispatchEvent(event, owner, null);
	}
	
	public void bindSkill(Skill skill, InvocationParameter param) {
		for (InvocationEvent event : skill.getAvailableHandlers()) {
			registerEventListener(event, skill.getDispatcherEntries(event));
		}
	}
	
	public void unbindSkill(Skill skill) {
		for (InvocationEvent event : skill.getEntries().keySet()) {
			unregisterEventListener(event, skill.getEntries().get(event));
		}
	}
	
	public void dispatchEvent(InvocationEvent event, BattleActor owner, EventParameter param) {
		boolean called = false;
		SortedSet<DispatcherEntry> eventList = eventTable.get(event);
		
		if (eventList == null) return;	// skip it if no handler is available.
		
		for (DispatcherEntry entry : eventList) {
			InvocationParameter params = entry.getParams();
			if (entry.getCondition().shouldInvoke(
					params.getRank(), 
					owner, 
					params.getTarget(),
					param)) {
				
				Collection<ActionProduct> aps = entry.getAction().act(
						params.getRank(), 
						owner, 
						params.getTarget(),
						param);
				
				ActionProductProcessor.process(aps, params, response);
				called = true;
			}
			if (called && event.equals(InvocationEvent.STD_IE_ON_ATTACK)) {
				break;
			}
		}
		if (event.equals(InvocationEvent.STD_IE_ON_ATTACK) && called == false) {
			// in case of invocation and it fails on every condition;
			// force use the Normal Attack of specific class.
			Skill sk = LogicObjectFactory.getSkillByName("normal attack");
			InvocationEntry ie = sk.getInvocationEntryOn(InvocationEvent.STD_IE_ON_ACTIVE);
			if (ie.getCondition().shouldInvoke(0, owner, sk.getDefaultTarget(), param)) {
				ie.getAction().act(0, owner, sk.getDefaultTarget(), param);
			}
		}
	}
	
	public void registerEventListener(InvocationEvent event, Collection<DispatcherEntry> entry) {
		for (DispatcherEntry e : entry) {
			registerEventListener(event, e);
		}
	}
	
	public void registerEventListener(InvocationEvent event, DispatcherEntry entry) {
		if (!eventTable.containsKey(event)) {
			eventTable.put(event, new TreeSet<DispatcherEntry>());
		}
		eventTable.get(event).add(entry);
	}
	
	public void unregisterEventListener(InvocationEvent event, InvocationEntry entry) {
		if (eventTable.containsKey(event)) {
			eventTable.get(event).remove(entry);
			if (eventTable.get(event).size()==0) {
				eventTable.remove(event);
			}
		}
	}

	public PostBattleResponseTO getCombatProduct() {
		return response;
	}

	public void setCombatProduct(PostBattleResponseTO combatProduct) {
		this.response = combatProduct;
	}
	
}
